package erfgame.core.world.terrain;

import java.util.logging.Logger;

import erfgame.core.Point3D;

public final class VerticalLightAngleVoxelColorPicker extends AbstractVoxelColorPicker {
	
	private static final Logger log = Logger.getLogger( VerticalLightAngleVoxelColorPicker.class.getName() );

	private static final double HORIZONTAL_LIGHT_ANGLE = -Math.PI/2;// -Math.PI/4;
	
	private static final int SIN_HORIZONTAL_LIGHT_ANGLE = (int)(Math.sin( HORIZONTAL_LIGHT_ANGLE )*ONE);
	private static final int ABS_SIN_HORIZONTAL_LIGHT_ANGLE = Math.abs( SIN_HORIZONTAL_LIGHT_ANGLE );
	
	private int[][] verticalAtanValues;
	private int[][] horizontalAtanValues;
	
	private double lightAngle;
	
	private TerrainColorSource unknownColorSource;
	
	private Point3D xzresult;
	private Point3D yzresult;
	private Point3D xyresult;
	
	public VerticalLightAngleVoxelColorPicker( double lightAngle, int smoothness, TerrainColorSource unknownColorSource, int blockWidth, int blockHeight, int blockDepth, boolean useLog ) {
		
		super( smoothness, blockWidth, blockHeight, blockDepth, useLog );
		this.verticalAtanValues = generateAtanValues( lightAngle, smoothness, false );
		this.horizontalAtanValues = generateAtanValues( HORIZONTAL_LIGHT_ANGLE, smoothness, false );
		this.unknownColorSource = unknownColorSource;
		this.lightAngle = lightAngle;
		
		this.xzresult = new Point3D();
		this.yzresult = new Point3D();
		this.xyresult = new Point3D();
	}
	
	public final int atan2sinVertical( int a, int b ) {
		return verticalAtanValues[b + smoothnessTimes2][a + smoothnessTimes2];
	}
	
	public final int atan2sinHorizontal( int a, int b ) {
		return horizontalAtanValues[b + smoothnessTimes2][a + smoothnessTimes2];
	}
	
	public final int getColor( 
			TerrainColorSource colorSource,
			int cx, 
			int cy, 
			int cz,
			byte[][][][][][] terrain, 
			int tx, 
			int ty, 
			int tz, 
			int dCeiling, 
			int worldX, 
			int worldY, 
			int worldZ,
			boolean clearRoof
	) {
		Point3D xzresult = this.xzresult;
		Point3D yzresult = this.yzresult;
		Point3D xyresult = this.xyresult;
		
		// TODO use internal values
		VerticalEdge xzturn = new VerticalEdge();
		VerticalEdge yzturn = new VerticalEdge();
		
		boolean xzValid = getPathDeltas(
				cx, 
				cy, 
				cz, 
				terrain, 
				tx, 
				ty, 
				tz, 
				XZ_LEFT_HANDED, 
				XZ_RIGHT_HANDED, 
				false,
				xzresult,
				xzturn,
				clearRoof
		);

		boolean yzValid = getPathDeltas(
				cx, 
				cy, 
				cz, 
				terrain, 
				tx, 
				ty, 
				tz, 
				YZ_LEFT_HANDED, 
				YZ_RIGHT_HANDED, 
				false,
				yzresult,
				yzturn,
				clearRoof
		);
		
		int mult;

		int xycx;
		int xycy;
		int xycz;
		int xytx;
		int xyty;
		int xytz;
		int xyremainingLength;
		if( (xzturn.remainingLength != 0 || yzturn.remainingLength != 0) ) {
			if( xzturn.remainingLength > yzturn.remainingLength ) {
				xytx = xzturn.x;
				xyty = xzturn.y;
				xytz = xzturn.z;
				xycx = xzturn.cx;
				xycy = xzturn.cy;
				xycz = xzturn.cz;
				xyremainingLength = xzturn.remainingLength;
			} else {
				xytx = yzturn.x;
				xyty = yzturn.y;
				xytz = yzturn.z;
				xycx = yzturn.cx;
				xycy = yzturn.cy;
				xycz = yzturn.cz;
				xyremainingLength = yzturn.remainingLength;
			}
		} else {
			xytx = tx;
			xyty = ty;
			xytz = tz;
			xycx = cx;
			xycy = cy;
			xycz = cz;
			xyremainingLength = smoothnessMinus1;
		}
		
		boolean xyValid;
		
		if( smoothnessMinus1 - xyremainingLength < 4 ) {
			xyValid = getPathDeltas(
					xycx, 
					xycy, 
					xycz, 
					terrain, 
					xytx, 
					xyty, 
					xytz, 
					XY_LEFT_HANDED, 
					XY_RIGHT_HANDED, 
					false,
					xyresult,
					null,
					clearRoof
			);	
		} else {
			// too far away
			xyValid = false;
		}
		
    	if( xyValid && xzValid && yzValid ) {
			// pick best two 
			if( yzresult.y == 0 ) {
				// the light angle is not flat, so use the other values
				yzValid = false;
			} else {
				int xyQuality = Math.abs(xyresult.x);
				int xzQuality = Math.abs(xzresult.x);
				if( xyQuality > xzQuality ) {
	                xzValid = false;
				} else {
					xyValid = false;
				}				
			}
		}
			
		if( yzValid && xzValid ) {
//			if( xyValid ) {
//				if(Math.abs( xyresult.y ) > Math.abs( yzresult.y ) ) {
//					if( yzresult.y > 0 ) {
//						yzresult.y = Math.abs( xyresult.y );
//					} else {
//						yzresult.y = -Math.abs( xyresult.y );
//					}
//				}
//				if( Math.abs( xyresult.x ) > Math.abs( xzresult.x ) ) {
//					if( xzresult.x > 0 ) {
//						xzresult.x = Math.abs( xyresult.x );
//					} else {
//						xzresult.x = -Math.abs( xyresult.x );
//					}
//				}
//			}

			//  TODO should we use max z for both?
			int yz = atan2sinVertical( yzresult.z, yzresult.y );
			double angle = Math.atan2(xzresult.z, xzresult.x );
			double xz = Math.sin(angle + Math.PI/2);
			mult = (int)(yz * Math.abs( xz ));

		} else if( yzValid && xyValid ) {
			
			int yz = atan2sinVertical( yzresult.z, yzresult.y );
			int xy = atan2sinHorizontal(xyresult.y, xyresult.x );
			mult = (xy * Math.abs( yz )) >> PRECISION;

			
			// needs to change to the appropriate proportions if the horizontal lighting angle ever changes
//			if( xyValid ) {
//				int sinxy = atan2sinHorizontal(xyresult.y, xyresult.x);
//				mult = (sinxy * Math.abs( yz ) ) >> PRECISION;									
//			} else {
////				int sinxy = atan2sinHorizontal(0, 1);
////				mult = (sinxy * yz) >> PRECISION;			
//				double angle = Math.atan2(xzresult.z, xzresult.x);
//				double xz = Math.sin(angle + Math.PI/2);
//				mult = (int)(yz * Math.abs( xz ));
//			}
			
		} else if( xzValid && xyValid ) {
			double maxLight = Math.cos( lightAngle ); 
			// TODO xy result could be useful here
       		int xy = atan2sinHorizontal(xyresult.y, xyresult.x );
			double angle = Math.atan2(xzresult.z, xzresult.x);
			double xz = Math.sin(angle);
			mult = (int)(xy * Math.abs( xz ) * maxLight);
//			int xz = atan2sinVertical(xzresult.z, xzresult.x);
//			if( xyValid ) {
//				int sinxy = atan2sinHorizontal(xyresult.y, xyresult.x );
//				mult = (sinxy * Math.abs(xz) ) >> PRECISION;					
//			} else {
//				int sinxy = atan2sinHorizontal(1, 0);
//				mult = (sinxy * xz) >> PRECISION;									
//			}
		} else {
			return this.unknownColorSource.getColor(worldX, worldY, worldZ, 0, 0, dCeiling);				
		}
		
		
//			double angle = Math.atan2( xyresult.y, xyresult.x );
//			double sinxy = Math.sin( angle - Math.PI/4 );
			
//			if( yzValid ) {
//				int yz;
//				if( yzresult.y == 0 && yzresult.z == 0 ) {
//					yz = 0;
//				} else {
//					yz = atan2sinVertical( yzresult.z, yzresult.y );
//				}			
//				if( xyValid ) {
//					mult = (yz * sinxy) >> PRECISION;
//				} else {
//					mult = (yz * ABS_SIN_HORIZONTAL_LIGHT_ANGLE) >> PRECISION;
//				}
//			} else if( xzValid ) {
//				int xz;
//				if( xzresult.x == 0 && xzresult.z == 0 ) {
//					xz = 0;
//				} else {
//					xz = atan2sinVertical( xzresult.z, xzresult.x );
//				}
//				if( xyValid ) {
//					mult = (xz * sinxy) >> PRECISION;
//				} else {
//					mult = (xz * ABS_SIN_HORIZONTAL_LIGHT_ANGLE) >> PRECISION;
//				}
//			} else {
//				return this.unknownColorSource.getColor(worldX, worldY, worldZ, 0, 0, dsurface);				
//			}			
		
		
			
		int color = colorSource.getColor( 
				worldX, 
				worldY, 
				worldZ, 
				mult, 
				PRECISION, 
				dCeiling 
		);
		
//		if( !diagValid ) {
//			color = 0xFFFF0000;
//		}
		
		return color;
	}	
}
